package com.googlecode.imanager.core.base;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import com.googlecode.imanager.client.Application;
import com.googlecode.imanager.client.Container;
import com.googlecode.imanager.client.Control;
import com.googlecode.imanager.client.Document;
import com.googlecode.imanager.client.Focusable;
import com.googlecode.imanager.client.Manager;
import com.googlecode.imanager.client.action.Action;
import com.googlecode.imanager.client.bridge.BridgeType;
import com.googlecode.imanager.client.bridge.IApplication;
import com.googlecode.imanager.client.bridge.IComponent;
import com.googlecode.imanager.client.bridge.IControl;
import com.googlecode.imanager.client.bridge.IFocusable;
import com.googlecode.imanager.client.exception.BridgeException;
import com.googlecode.imanager.client.exception.IMException;
import com.googlecode.imanager.client.io.Path;
import com.googlecode.imanager.client.media.Image;
import com.googlecode.imanager.client.style.StandardTheme;
import com.googlecode.imanager.client.style.Theme;
import com.googlecode.imanager.client.util.Logger;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class DefaultManager extends Manager {
    
    private Map<BridgeType, String> bridgeClasses;
    private BridgeApplication emptyApplication;
    private Map<Class<?>, Logger> loggers;
    
    public DefaultManager(){
        loggers = new HashMap<Class<?>, Logger>();
        bridgeClasses = new HashMap<BridgeType, String>();
        emptyApplication = new EmptyApplication(this);
        initialize();
    }
    
    protected void initialize() {
        registerBridgeApplication(BridgeType.EMPTY, "core.base.EmptyApplication");
        registerBridgeApplication(BridgeType.SWING, "swing.SwingApplication");
        registerBridgeApplication(BridgeType.PDF, "jasper.PdfApplication");
        registerBridgeApplication(BridgeType.XLS, "jasper.XlsApplication");
    }

    protected void registerBridgeApplication(BridgeType bridgeType, String className){
        registerBridgeApplication(bridgeType, null, className);
    }
    
    protected void registerBridgeApplication(BridgeType bridgeType, String packageName, String className){
        if (packageName == null){
            packageName = IMANAGER_PACKAGE;
        }
        bridgeClasses.put(bridgeType, packageName + "." + className);
    }
    
    private BridgeApplication allocateBridgeApplication(BridgeType type, Application application, OutputStream output) {
        BridgeApplication handler;
        String handlerClassName = bridgeClasses.get(type);
        Class<?> handlerClass;

        if (handlerClassName != null){

            try {
                handlerClass = Class.forName(handlerClassName);                
            } catch (ClassNotFoundException e) {
                throw new BridgeException(e);
            }

            try {
                handler = (BridgeApplication) handlerClass.getConstructor(
                                DefaultManager.class, 
                                Application.class,
                                OutputStream.class).newInstance(this, application, output);
            } catch (Exception e) {
                throw new BridgeException(e);
            }

        } else {
            throw new BridgeException("No such implementation for type " + type);
        }
        
        return handler;
    }

    /* FIXME @Override
    public IThemeHandler lookupThemeImplementation(Object source, StyleScope styleScope) {
        if (source instanceof Control){
            Control control = (Control) source;
            IThemeHandler theme = getThemeImpl(control, styleScope);
            
            if (theme == null){
                if (styleScope == null){
                    if (source instanceof HasTheme){
                        theme = new DefaultParentTheme((HasTheme) control);
                    } else {
                        return null;
                    }
                } else {
                    theme = new DefaultChildTheme(control, styleScope);
                }
                setThemeImpl(control, theme, styleScope);
                theme.refresh();
            }
            
            return theme;
        }
        
        if (source instanceof Application
                && styleScope == null){
            Application application = (Application) source;
            IParentThemeHandler theme = getThemeImpl(application);
            
            if (theme == null){
                theme = new DefaultParentTheme(application);
                setThemeImpl(application, theme);
                theme.refresh();
            }
            
            return theme;
        }
        
        return null;
    }*/

    @Override
    public IComponent lookupImplementation(Object source) {
        synchronized (IComponent.TREE_LOCK) {
            if (source instanceof Control){
                Control control = (Control) source;

                if (!hasImpl(control)){
                    getEmptyApplication().createImplementation(control).start();
                }
                
                return getImpl(control);
            }
            
            if (source instanceof Application){
                Application application = (Application) source;

                if (!hasImpl(application)){
                    setImpl(application, getEmptyApplication());
                }
                
                return getImpl(application);
            }

            throw new BridgeException("no such implementation for " + source.getClass());
        }
    }

    @Override
    public void generateBridge(Document document, BridgeType bridgeType, OutputStream output) throws IMException {
        if (bridgeType == null){
            bridgeType = BridgeType.EMPTY;
        }
        
        Application application = new Application();
        application.add(document);
        
        generateBridge(application, bridgeType, output);
    }

    @Override
    public void generateBridge(Application application, BridgeType bridgeType, OutputStream output) throws IMException {
        long ms = System.currentTimeMillis();
        
        if (bridgeType == null){
            bridgeType = BridgeType.EMPTY;
        }

        if (application.getImplementationType() != bridgeType){
            try {
                BridgeApplication handler;
                List<Document> documents;

                synchronized (IComponent.TREE_LOCK) {
                    
                    IApplication oldImp = getImpl(application);
                    
                    if (oldImp instanceof BridgeApplication
                            && oldImp.getImplementationType() != BridgeType.EMPTY){
                        ((BridgeApplication)oldImp).destroy();
                    }

                    if (bridgeType != BridgeType.EMPTY){
                        handler = allocateBridgeApplication(bridgeType, application, output);
                    } else {
                        handler = getEmptyApplication();
                    }
                    
                    handler.start();
                    documents = new ArrayList<Document>(application.getDocuments());
                    
                    for (Document doc : documents){
                        handler.beforeGenerateBridge(doc);
                        handler.generateBridge(doc);
                    }                    
                }

                for (Document doc : documents){
                    handler.afterGenerateBridge(doc);
                }
                
                getLogger().trace("generateBridge("+bridgeType+")", System.currentTimeMillis() - ms);

            } catch (Exception e) {
                throw new IMException(e);
            }
        }
    }

    public void generateParentBridgeImplementation(Container parent, BridgeApplication bridgeHandler) {
        Container root = parent.getParent();
        while (root != null){
            parent = root;
            root = parent.getParent();
        }
        generateChildrenBridgeImplementation(parent, bridgeHandler);
    }

    public void generateChildrenBridgeImplementation(Control owner, BridgeApplication bridgeHandler) {
        synchronized (IComponent.TREE_LOCK) {
            bridgeHandler.beforeValidateRootImplementation(owner);
            generateChildrenBridgeImplementationAux(owner, bridgeHandler);
            bridgeHandler.afterValidateRootImplementation(owner);
        }
    }
    
    private void generateChildrenBridgeImplementationAux(Control owner, BridgeApplication bridgeHandler) {
        
        if (hasImpl(owner)){
            BridgeControl target = (BridgeControl) getImpl(owner);
            BridgeType oldBridgeType = target.getImplementationType();
            
            if (oldBridgeType != bridgeHandler.getImplementationType()){
                bridgeHandler.beforeCreateImplementation(owner);

                target = (BridgeControl) getImpl(owner);
                if (target != null){

                    oldBridgeType = target.getImplementationType();
                    if (oldBridgeType == bridgeHandler.getImplementationType()){
                        return;
                    }

                    target.destroy();
                }

                bridgeHandler.createImplementation(owner).start();

                if (owner instanceof Container){            
                    for (Control child : ((Container) owner).getChildren()){
                        generateChildrenBridgeImplementationAux(child, bridgeHandler);
                    }
                }

                bridgeHandler.afterCreateImplementation(owner);
            }
            
        } else {

            bridgeHandler.beforeCreateImplementation(owner);
        
            if (hasImpl(owner)){
                BridgeControl target = (BridgeControl) getImpl(owner);
                BridgeType oldBridgeType = target.getImplementationType();

                if (oldBridgeType == bridgeHandler.getImplementationType()){
                    return;
                }
            }

            bridgeHandler.createImplementation(owner).start();
            
            if (owner instanceof Container){            
                for (Control child : ((Container) owner).getChildren()){
                    generateChildrenBridgeImplementationAux(child, bridgeHandler);
                }
            }

            bridgeHandler.afterCreateImplementation(owner);
        }
    }

    @Override
    public void changeApplication(Document child, Application parent) {
        try {
            BridgeApplication handler;
            
            synchronized (IComponent.TREE_LOCK) {
                
                if (getApplication(child) != parent){
                    throw new IllegalStateException();
                }

                if (parent == null){
                    return;
                }
                
                handler = (BridgeApplication) lookupImplementation(parent);            
                handler.beforeGenerateBridge(child);
                handler.generateBridge(child);
            }
            
            handler.afterGenerateBridge(child);
            
        } catch (IllegalStateException e) {
            throw e;
        } catch (Exception e) {
            throw new BridgeException(e);
        }
    }
    
    @Override
    public void changeParent(Control child, Container parent) {
        if (child.getParent() != parent){
            throw new IllegalStateException();
        }
        
        if (parent != null){            
            BridgeApplication childHandler;
            BridgeApplication parentHandler;
            
            IControl controlImpl = getImpl(parent);
            if (controlImpl != null){
                parentHandler = ((BridgeControl) controlImpl).getBridgeApplication();
            } else {
                parentHandler = getEmptyApplication();
            }
            
            controlImpl = getImpl(child);
            if (controlImpl != null){
                childHandler = ((BridgeControl) controlImpl).getBridgeApplication();
            } else {
                childHandler = getEmptyApplication();
            }

            if (childHandler.getImplementationType() != parentHandler.getImplementationType()){
            
                if (parentHandler.getImplementationType() == BridgeType.EMPTY){
                    // generate bridge implementation for the parent 
                    generateParentBridgeImplementation(parent, childHandler);
                }
                else {
                    // generate bridge implementation for the children 
                    generateChildrenBridgeImplementation(child, parentHandler);
                }
                return;
            } 
        }
    }

    public BridgeApplication getEmptyApplication() {
        return emptyApplication;
    }

    @Override
    public Image loadImage(Document owner, Path path) {
        return new DefaultImage(owner, path);
    }

    @Override
    public boolean executeAction(Action action, Control sender) {
        Application app = sender.getApplication();
        if (app != null){
            BridgeApplication impl = (BridgeApplication) getImpl(app);
            
            if (impl != null){
                return impl.executeAction(action, sender);
            }
        }
        return false;
    }

    @Override
    public Logger getLogger(Class<?> ownerClass) {
        Logger logger = loggers.get(ownerClass);
        if (logger == null){
            logger = new DefaultLogger(ownerClass);
            loggers.put(ownerClass, logger);
        }
        return logger;
    }

    private Logger getLogger() {
        return getLogger(DefaultManager.class);
    }

    @Override
    public void setFocusControl(Application application, Focusable focusControl) {
        Focusable oldFocused = application.getFocusControl();
        if (oldFocused != focusControl){
            if (application.getImplementationType() == BridgeType.EMPTY){
                if (focusControl == null){
                    fireBlur(oldFocused);
                } else {
                    fireFocus(focusControl);
                }
            } else {
                if (focusControl == null){
                    IFocusable control = (IFocusable) getImpl((Control) oldFocused);
                    control.blur();
                } else {
                    IFocusable control = (IFocusable) getImpl((Control) focusControl);
                    control.focus();
                }
            }
        }
    }

    @Override
    public Theme getDefaultTheme(BridgeType type) {
        // TODO um Theme default pra cada tipo do impl
        return StandardTheme.getInstance();
    }

    @Override
    public String getControlSuperClass(String controlClassName) {
        try {
            Class<?> c = Class.forName(controlClassName);
            
            if (c == Control.class
                    || c == Object.class){
                return null;
            }
            
            return c.getSuperclass().getName();
            
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public Iterator<String> getControlHierarchy(String controlClassName) {
        try {
            Class<?> c = Class.forName(controlClassName);
            return new HierarchyIterator(c);
            
        } catch (Exception e) {
            return Collections.<String>emptySet().iterator();
        }
    }

    
    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    private static class HierarchyIterator implements Iterator<String> {

        private Class<?> type;
        
        public HierarchyIterator(Class<?> type) {
            this.type = type;
        }

        @Override
        public boolean hasNext() {
            return type != null;
        }

        @Override
        public String next() {
            if (type == null){
                throw new NoSuchElementException();
            }
            
            Class<?> next = type;
            type = type.getSuperclass();
            
            if (type == Control.class.getSuperclass()){
                type = null;
            }
            
            return next.getName();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
        
    }
    
}
